
/**
 * Date:2019年10月28日下午4:17:42
 * Copyright (c) 2019, www.bwbroad.com All Rights Reserved.
 *
*/

package com.xue.common.log;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;

/**
 * Description: 为了给tomcat打印access_log日志时，打印requet body 参考
 * AbstractRequestLoggingFilter,为了能否访问spring采用了 DelegatingFilterProxy <br/>
 * Date: 2019年11月12日 下午4:17:42 <br/>
 * 
 * @author xuejianxin
 */
public class TomcatAccessLogFilter implements Filter {
	private static final Logger logger = LoggerFactory.getLogger(TomcatAccessLogFilter.class);

	public static final String TAG = "_tag_";
	private final String CONTENT_TYPE = "content-type";
	private final String CONTENT_TYPE_MULTI = "multipart";
	private final String CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
	private final String BODY = "_body_";
	private final String USERNAME = "_username_";
	private final String SHUT_DOWN_LOG = "shutdownlog";

	@Autowired
	private RequestMappingHandlerMapping requestMappingHandlerMapping;
	private String sessionUserKey = "username";
	private int maxPayloadLength = 500 * 3;// 这里是字节数量,按utf-8 的字符数*3=字节数量 估算
	private volatile boolean shutdown = false;
	private Map<String, String> requestMappingInfoMap;
	private boolean isLogTag = false;

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {
		String bl = filterConfig.getInitParameter("maxPayloadLength");
		String userKey = filterConfig.getInitParameter("sessionUserKey");
		if (!StringUtils.isEmpty(bl)) {
			this.maxPayloadLength = Integer.valueOf(bl);
		}
		if (StringUtils.isEmpty(userKey)) {
			this.sessionUserKey = userKey;
		}
		initRequestMappingInfoMap();
	}

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) servletRequest;
		setShutdown(request);
		if (shutdown) {
			chain.doFilter(request, response);
			return;
		} else {
			HttpServletRequest requestWrapper = request;
			if (!(request instanceof ContentCachingRequestWrapper)) {
				requestWrapper = new ContentCachingRequestWrapper(request);
			}
			chain.doFilter(requestWrapper, response);
			try {
				logAccess(requestWrapper);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

	}

	private void initRequestMappingInfoMap() {
		if (null != requestMappingHandlerMapping) {
			requestMappingInfoMap = new HashMap<>();
			Map<RequestMappingInfo, HandlerMethod> map = requestMappingHandlerMapping.getHandlerMethods();
			for (RequestMappingInfo info : map.keySet()) {
				PatternsRequestCondition p = info.getPatternsCondition();
				for (String url : p.getPatterns()) {
					requestMappingInfoMap.put(url, info.getName());
				}
			}
			this.isLogTag = !requestMappingInfoMap.isEmpty();
		}
	}

	private void setShutdown(HttpServletRequest request) {
		// 在不重启应用的情况下动态开启和关闭log,传递参数shutdownlog=false 关闭
		String sdl = request.getParameter(SHUT_DOWN_LOG);
		if (StringUtils.hasText(sdl)) {
			if (Boolean.TRUE.toString().equalsIgnoreCase(sdl)) {
				this.shutdown = true;
			} else if (Boolean.FALSE.toString().equalsIgnoreCase(sdl)) {
				this.shutdown = false;
			}
			logger.info(SHUT_DOWN_LOG + "=" + sdl + " ,shutdown=" + this.shutdown);
		}
	}

	private void logAccess(HttpServletRequest request) {
		String contentType = request.getHeader(CONTENT_TYPE);
		boolean isMultipart = false;// 是否上传文件的请求
		boolean isFormData = false;// 是否普通form提交
		if (StringUtils.hasText(contentType)) {
			if (contentType.startsWith(CONTENT_TYPE_MULTI)) {
				isMultipart = true;
			} else if (contentType.startsWith(CONTENT_TYPE_FORM)) {
				isFormData = true;
			}
		}

		String body = "";
		if (isMultipart) {
			body = "[" + CONTENT_TYPE_MULTI + "]";
		} else {
			body = getMessagePayload(request);
			if (isFormData) {// 请求正文转化为中文
				try {
					body = URLDecoder.decode(body, "UTF-8");
				} catch (UnsupportedEncodingException e) {
					e.printStackTrace();
				}
			}
		}

		// 记录body
		request.setAttribute(BODY, body);
		// 记录tag
		if (this.isLogTag) {
			String url = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
			if (StringUtils.hasText(url)) {
				request.setAttribute(TAG, requestMappingInfoMap.get(url));
			}
		}

		// 记录用户名
		HttpSession session = request.getSession(false);
		if (null != session) {
			request.setAttribute(USERNAME, session.getAttribute(this.sessionUserKey));
		}
	}

	// 获取请求正文
	private String getMessagePayload(HttpServletRequest request) {
		ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
		if (wrapper != null) {
			byte[] buf = wrapper.getContentAsByteArray();
			if (buf.length > 0) {
				int length = Math.min(buf.length, this.maxPayloadLength);
				try {
					return new String(buf, 0, length, wrapper.getCharacterEncoding());
				} catch (UnsupportedEncodingException ex) {
					return "[unknown]";
				}
			}
		}
		return null;
	}

}
